// <copyright file="Menu.cs" company="YUIAsp.Net">
// Copyright (c) 2009 All Right Reserved
// </copyright>
// <author>raul popescu</author>
// <email>dev@yuiasp.net</email>
// <date>2009-01-30</date>
// <summary>YUIAsp.Net File</summary>

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Text;
using System.Web.UI;
using System.Web.UI.WebControls;
using YUIAspNet.Common;

#region Menu assets registration
[assembly: WebResource("YUIAspNet.Menu.Menu.js", "text/javascript")]

#region Default skin
[assembly: WebResource("YUIAspNet.EmbeddedSkins.DefaultSkin.Menu.menu.css", "text/css", PerformSubstitution = true)]
[assembly: WebResource("YUIAspNet.EmbeddedSkins.DefaultSkin.Menu.menu-down-arrow.png", "image/png")]
[assembly: WebResource("YUIAspNet.EmbeddedSkins.DefaultSkin.Menu.menu-down-arrow-disabled.png", "image/png")]
[assembly: WebResource("YUIAspNet.EmbeddedSkins.DefaultSkin.Menu.menu-up-arrow.png", "image/png")]
[assembly: WebResource("YUIAspNet.EmbeddedSkins.DefaultSkin.Menu.menu-up-arrow-disabled.png", "image/png")]
[assembly: WebResource("YUIAspNet.EmbeddedSkins.DefaultSkin.Menu.menubaritem-submenuindicator.png", "image/png")]
[assembly: WebResource("YUIAspNet.EmbeddedSkins.DefaultSkin.Menu.menubaritem-submenuindicator-disabled.png", "image/png")]
[assembly: WebResource("YUIAspNet.EmbeddedSkins.DefaultSkin.Menu.menubaritem-submenuindicator-selected.png", "image/png")]
[assembly: WebResource("YUIAspNet.EmbeddedSkins.DefaultSkin.Menu.menuitem-checkbox.png", "image/png")]
[assembly: WebResource("YUIAspNet.EmbeddedSkins.DefaultSkin.Menu.menuitem-checkbox-disabled.png", "image/png")]
[assembly: WebResource("YUIAspNet.EmbeddedSkins.DefaultSkin.Menu.menuitem-checkbox-selected.png", "image/png")]
[assembly: WebResource("YUIAspNet.EmbeddedSkins.DefaultSkin.Menu.menuitem-submenuindicator.png", "image/png")]
[assembly: WebResource("YUIAspNet.EmbeddedSkins.DefaultSkin.Menu.menuitem-submenuindicator-disabled.png", "image/png")]
[assembly: WebResource("YUIAspNet.EmbeddedSkins.DefaultSkin.Menu.menuitem-submenuindicator-selected.png", "image/png")] 
#endregion

#region Sam skin
[assembly: WebResource("YUIAspNet.EmbeddedSkins.Sam.Menu.menu.css", "text/css", PerformSubstitution = true)]
[assembly: WebResource("YUIAspNet.EmbeddedSkins.Sam.Menu.menubaritem_submenuindicator.png", "image/png")]
[assembly: WebResource("YUIAspNet.EmbeddedSkins.Sam.Menu.menubaritem_submenuindicator_disabled.png", "image/png")]
[assembly: WebResource("YUIAspNet.EmbeddedSkins.Sam.Menu.menuitem_checkbox.png", "image/png")]
[assembly: WebResource("YUIAspNet.EmbeddedSkins.Sam.Menu.menuitem_checkbox_disabled.png", "image/png")]
[assembly: WebResource("YUIAspNet.EmbeddedSkins.Sam.Menu.menuitem_submenuindicator.png", "image/png")]
[assembly: WebResource("YUIAspNet.EmbeddedSkins.Sam.Menu.menuitem_submenuindicator_disabled.png", "image/png")]
#endregion

#endregion

namespace YUIAspNet
{
    /// <summary>
    /// Args class for Menu's click event.
    /// </summary>
    public class MenuClickEventArgs : EventArgs
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="MenuClickEventArgs"/> class.
        /// </summary>
        /// <param name="item">The item.</param>
        public MenuClickEventArgs(MenuItem item)
        {
            this.Item = item;
        }

        /// <summary>
        /// Gets or sets the item that was clicked.
        /// </summary>
        /// <value>The clicked item.</value>
        public MenuItem Item { get; private set; }
    }

    /// <summary>
    /// Menu control.
    /// </summary>
    [ToolboxItem(true)]
    public class Menu : YUIScriptBaseControl, INamingContainer, IPostBackDataHandler
    {
        Panel _menuPanel;
        private IList<MenuItem> _menuItemList;

        /// <summary>
        /// Occurs when one of the menu items was clicked.
        /// </summary>
        public event EventHandler<MenuClickEventArgs> Click;

        /// <summary>
        /// Initializes a new instance of the <see cref="Menu"/> class.
        /// </summary>
        public Menu() : base("div")
        {

            MenuType = MenuType.Vertical;
            AutoSubMenuDisplay = true;

            PreRender += new EventHandler(Menu_PreRender);
        }

        /// <summary>
        /// Called by the ASP.NET page framework to notify server controls that use composition-based implementation to create any child controls they contain in preparation for posting back or rendering.
        /// </summary>
        protected override void CreateChildControls()
        {
            _menuPanel = new Panel();
            _menuPanel.ID = "menu";
            _menuPanel.CssClass = "menuContainer";
            Controls.Add(_menuPanel);            

            base.CreateChildControls();
        }

        void Menu_PreRender(object sender, EventArgs e)
        {
            if ((this.Page != null))
                this.Page.RegisterRequiresPostBack(this);
        }

        /// <summary>
        /// Registers the style sheets for the current control.
        /// Must be overriden in the parent control to add any css file reference.
        /// </summary>
        protected override void RegisterStyleSheets()
        {
            //RegisterEmbeddedStylesheet("YUIAspNet.EmbeddedSkins.Menu.DefaultSkin.menu.css");
            RegisterEmbeddedStylesheet("Menu", "menu.css");
        }

        /// <summary>
        /// Register the values to send to the client control for initialization.
        /// </summary>
        protected override void RegisterClientInitialization()
        {
            AddProperty("MenuContainerID", this._menuPanel.ClientID);
            AddProperty("MenuType", this.MenuType.ToString().ToLowerInvariant());
            AddProperty("ToggleElementID", this.ToggleElementID); // only for dynamic!
            AddProperty("Enabled", this.Enabled);
            AddProperty("ShowCentered", this.ShowCentered);
            AddProperty("ClassName", this.CssClass);

            StringBuilder sbItems = ConvertMenuItems(this.Items);

            //if (this.Groups != null && this.Groups.Count > 0)
            //{
            //    foreach (var gp in this.Groups)
            //    {
            //        sbItems.AppendFormat(",[{0}]", ConvertMenuItems(gp.MenuItems));
            //    }
            //}

            AddProperty("Items", sbItems, true);
            //AddProperty("Groups", GetItemGroups(this.Groups), true);

            AddProperty("AutoSubMenuDisplay", AutoSubMenuDisplay);
            AddProperty("ClickToHide", ClickToHide);
            AddProperty("ConstrainToViewport", ConstrainToViewport);
            AddProperty("HideDelay", HideDelay);
            AddProperty("UseIFrame", UseIFrame);
            AddProperty("KeepOpen", KeepOpen);
            AddProperty("MaxHeight", MaxHeight);
            AddProperty("MinScrollHeight", MinScrollHeight);
            AddProperty("PreventContextOverlap", PreventContextOverlap);
            AddProperty("ScrollIncrement", ScrollIncrement);
            AddProperty("ShowShadow", ShowShadow);
            AddProperty("SubMenuHideDelay", SubMenuHideDelay);
            AddProperty("Effect", Effect);
            AddProperty("LazyLoad", LazyLoad);
        }

        //private StringBuilder GetItemGroups(List<MenuGroup> groups)
        //{
        //    StringBuilder sb = new StringBuilder();


        //    return sb;
        //}

        private int idx;

        private StringBuilder ConvertMenuItems(IEnumerable<MenuItem> items)
        {
            StringBuilder sb = new StringBuilder("[");

            foreach(var item in items)
            {
                ConvertItem(sb, item);
            }

            return sb.Append("]");
        }

        private void ConvertItem(StringBuilder sb, MenuItem item)
        {
            sb.AppendFormat(CultureInfo.InvariantCulture, "{0}{{", sb.Length > 1 ? "," : "");

            sb.Append(YUIJavascriptSerializer.SerializeProperty("text", item.Text))
                .Append(",").Append(YUIJavascriptSerializer.SerializeProperty("value", item.Value ?? item.Text));
            if (item.Selected)
                sb.Append(",").Append(YUIJavascriptSerializer.SerializeProperty("selected", item.Selected));
            if (item.Checked)
                sb.Append(",").Append(YUIJavascriptSerializer.SerializeProperty("checked", item.Checked));
            if (!item.Enabled)
                sb.Append(",").Append(YUIJavascriptSerializer.SerializeProperty("disabled", !item.Enabled));
            if (item.NavigateUrl != null)
                sb.Append(",").Append(YUIJavascriptSerializer.SerializeProperty("url", item.NavigateUrl));
            if (item.Target != null)
                sb.Append(",").Append(YUIJavascriptSerializer.SerializeProperty("target", item.Target));
            if (item.CssClass != null)
                sb.Append(",").Append(YUIJavascriptSerializer.SerializeProperty("classname", item.CssClass));

            if (item.AutoPostback)
                sb.Append(",").Append(YUIJavascriptSerializer.SerializeProperty("AutoPostback", item.AutoPostback));

            if (item.OnClientClick != null)
                sb.AppendFormat(CultureInfo.InvariantCulture, ",OnClientClick: {0}", item.OnClientClick);

            if (item.Items != null && item.Items.Count > 0)
            {
                idx++;
                sb.AppendFormat(CultureInfo.InvariantCulture, ",submenu: {{id: 'sub_{0}', itemdata: {1}}}", idx, ConvertMenuItems(item.Items));
            }
            if (item.Groups != null && item.Groups.Count > 0)
            {
                sb.Insert(0, "[");
                foreach(var gp in item.Groups)
                    sb.AppendFormat(CultureInfo.InvariantCulture, ",[{0}]", ConvertMenuItems(gp.Items));
                sb.Append("]");
            }

            sb.Append("}");
        }

        /// <summary>
        /// Registers any additional scripts to include for this control.
        /// </summary>
        protected override void RegisterScriptReferences()
        {
            AddScriptReference(new ScriptReference("YUIAspNet.Menu.Menu.js", this.GetType().Assembly.FullName));
        }

        #region Public properties
        /// <summary>
        /// Get / set the menu's items.
        /// </summary>
        [Category("Data"), Description("Menu items.")]
        [PersistenceMode(PersistenceMode.InnerProperty)]
        public IList<MenuItem> Items 
        {
            get
            {
                if(_menuItemList == null)
                    _menuItemList = new List<MenuItem>();
                return _menuItemList;
            }
        }

        /// <summary>
        /// Gets or sets the type of the menu.
        /// </summary>
        /// <value>The type of the menu.</value>
        public MenuType MenuType { get; set; }

        /// <summary>
        /// Gets or sets the toggle element ID.
        /// </summary>
        /// <value>The toggle element ID.</value>
        public string ToggleElementID { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether to show the menu centered.
        /// </summary>
        /// <value><c>true</c> to show centered menu; otherwise, <c>false</c>.</value>
        public bool ShowCentered { get; set; }

        /// <summary>
        /// Indicates if submenus are automatically made visible when the user mouses over the menu's items.
        /// </summary>
        public bool AutoSubMenuDisplay { get; set; }

        /// <summary>
        /// Indicates if the menu will automatically be hidden if the user clicks outside of it. 
        /// This property is only applied when the position property is set to "dynamic" and is automatically applied to all submenus.
        /// </summary>
        public bool ClickToHide { get; set; }

        /// <summary>
        /// Indicates if the menu will try to remain inside the boundaries of the size of viewport. 
        /// This property is only applied when the "position" configuration property is set to dynamic and is automatically applied to all submenus.
        /// </summary>
        public bool ConstrainToViewport { get; set; }

        /// <summary>
        /// Number indicating the time (in milliseconds) that should expire before the menu is hidden. 
        /// This property is only applied when the "position" configuration property is set to dynamic and is automatically applied to all submenus.
        /// </summary>
        public int HideDelay { get; set; }

        /// <summary>
        /// If set to true, menu will have an iframe to prevent SELECT elements to display on top of the menu.
        /// </summary>
        public bool UseIFrame { get; set; }

        /// <summary>
        /// Indicates if the menu should remain open when clicked.
        /// </summary>
        public bool KeepOpen { get; set; }

        /// <summary>
        /// Number defining the maximum height (in pixels) for a menu's body element
        /// </summary>
        public int MaxHeight { get; set; }

        /// <summary>
        /// Gets or sets the minimum scroll height.
        /// </summary>
        public int MinScrollHeight { get; set; }

        /// <summary>
        /// Boolean indicating whether or not a submenu should overlap its parent MenuItem when the "constraintoviewport" 
        /// configuration property is set to "true".
        /// </summary>
        public bool PreventContextOverlap { get; set; }

        /// <summary>
        /// Number used to control the scroll speed of a menu. Used to increment the "scrollTop" 
        /// property of the menu's body by when a menu's content is scrolling. When set this property is automatically applied to all submenus.
        /// </summary>
        public int ScrollIncrement { get; set; }

        /// <summary>
        /// Boolean indicating if the menu should have a shadow.
        /// </summary>
        public bool ShowShadow { get; set; }

        /// <summary>
        /// Number indicating the time (in milliseconds) that should expire before a submenu is made 
        /// visible when the user mouses over the menu's items. This property is only applied when the "position" 
        /// configuration property is set to dynamic and is automatically applied to all submenus.
        /// </summary>
        public int ShowDelay { get; set; }

        /// <summary>
        /// Number indicating the time (in milliseconds) that should expire before a submenu is hidden when the user mouses 
        /// out of a menu item heading in the direction of a submenu. The value must be greater than or equal to the value 
        /// specified for the "showdelay" configuration property. This property is only applied when the "position" configuration 
        /// property is set to dynamic and is automatically applied to all submenus.
        /// </summary>
        public int SubMenuHideDelay { get; set; }

        /// <summary>
        /// Effect to apply when showing/hiding the menu.
        /// </summary>
        [PersistenceMode(PersistenceMode.InnerProperty)]
        public ContainerEffect Effect { get; set; }

        /// <summary>
        /// Boolean indicating if the menu's "lazy load" feature is enabled. If set to "true," 
        /// initialization and rendering of the menu's items will be deferred until the first time it is made visible. 
        /// This property should be set via the constructor using the configuration object literal.
        /// </summary>
        public bool LazyLoad { get; set; }

        #endregion

        #region IPostBackDataHandler Members

        /// <summary>
        /// When implemented by a class, processes postback data for an ASP.NET server control.
        /// </summary>
        /// <param name="postDataKey">The key identifier for the control.</param>
        /// <param name="postCollection">The collection of all incoming name values.</param>
        /// <returns>
        /// true if the server control's state changes as a result of the postback; otherwise, false.
        /// </returns>
        public bool LoadPostData(string postDataKey, System.Collections.Specialized.NameValueCollection postCollection)
        {
            return postCollection["__EVENTTARGET"] == this.ClientID;
        }

        /// <summary>
        /// When implemented by a class, signals the server control to notify the ASP.NET application that the state of the control has changed.
        /// </summary>
        public void RaisePostDataChangedEvent()
        {
            if (this.Click != null)
            {
                MenuItem item = this.FindItemByValue(this.Page.Request.Form["__EVENTARGUMENT"], this.Items) ??
                                this.FindItemByText(this.Page.Request.Form["__EVENTARGUMENT"], this.Items);

                this.Click(this, new MenuClickEventArgs(item));
            }
        }

        /// <summary>
        /// Finds the item by text.
        /// </summary>
        /// <param name="text">The text.</param>
        /// <param name="items">The items.</param>
        /// <returns></returns>
        public MenuItem FindItemByText(string text, IList<MenuItem> items)
        {
            MenuItem mi = null;
            foreach (var item in items)
            {
                if (item.Text == text)
                {
                    mi = item;
                    break;
                }

                if (item.Items != null && item.Items.Count > 0)
                {
                    mi = FindItemByValue(text, items);
                    if (mi != null) break;
                }
            }

            return mi;
        }

        /// <summary>
        /// Finds the item by value.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="items">The items.</param>
        /// <returns></returns>
        public MenuItem FindItemByValue(string value, IList<MenuItem> items)
        {
            MenuItem mi = null;
            foreach (var item in items)
            {
                if (item.Value == value)
                {
                    mi = item;
                    break;
                }

                if (item.Items != null && item.Items.Count > 0)
                {
                    mi = FindItemByValue(value, items);
                    if (mi != null) break;
                }
            }

            return mi;
        }

        #endregion
    }
}